/*                   _   _ _ __                               *\
**     ________ ___ | | | | '_ \   scaup - Scala up!          **
**    / __/ __// _ || | | | |_) |  (c) 2008, Normen Mueller   **
**  __\ \/ /__/ __ || |_| | .__/   http://kwarc.info.nmueller **
** /____/\___/_/ |_/ \__,_|_|                                 **
**                                                            **
\*                                                            */
  
// $Id$

/* This class has its origin at: scala.tools.nsc.Settings */

package scaup

import java.io.File
import java.lang.System

class Settings(error: String => Unit) {
  def this() = this(Console.println)
  
  private var allsettings: List[Setting] = List()
  
  private def getProperty(name: String): String = if (System.getProperty(name) != "") System.getProperty(name) else null
  
  private val classpathDefault =
    if (System.getProperty("env.classpath") ne null) alternatePath(getProperty("env.classpath"), ".") else getProperty("java.class.path")
  
  private def alternatePath(p1: String, p2: => String) = if (p1 ne null) p1 else p2  
  
  val nowarnings    = BooleanSetting    ("-nowarn", "Generate no warnings")
  val prompt        = BooleanSetting    ("-Xprompt", "Display a prompt after each error (debugging option)")
  val verbose       = BooleanSetting    ("-verbose", "Output messages about what the program is doing")
  val version       = BooleanSetting    ("-version", "Print product version and exit")
  val help          = BooleanSetting    ("-help", "Print a synopsis of standard options")
  val debug         = BooleanSetting    ("-Ydebug", "Output debugging messages")
  
  /** A list of all settings */
  def allSettings: List[Setting] = allsettings.reverse
  
  /** Disable a setting */
  def disable(s: Setting) = allsettings = allsettings filter (s !=)
  
  /** Check dependencies */
  def checkDependencies: Boolean = {
    def hasValue(s: Setting, value: String): Boolean = s match {
      case bs: BooleanSetting => bs.value
      case ss: StringSetting  => ss.value == value
      case cs: ChoiceSetting  => cs.value == value
      case _ => "" == value
    }
    var ok = true
    for (setting <- allsettings if !setting.dependency.isEmpty) {
      val (dep, value) = setting.dependency.get
      if (! (setting.isDefault || hasValue(dep, value))) {
        error("incomplete option " + setting.name + " (requires " + dep.name + ")")
        ok = false
      }
    }
    ok
  } 
  
  override def equals(that: Any) = that match {
    case s:Settings =>  List.forall2(this.allSettings, s.allSettings)(_==_)
    case _ => false
  }

  /** A base class for settings of all types. Subclasses each define a `value' field of the appropriate type. */
  abstract class Setting(descr: String) {
    /** The name of the option as written on the command line, '-' included. */
    def name: String
    /** If first arg defines this setting, consume it as well as all following args needed to define the setting. If this can be done without error,
     * set value field and return suffix of args else issue error message and return the arguments unchanged. If first arg does not define this
     * setting return args unchanged. */
    def tryToSet(args: List[String]): List[String]
    /** The syntax defining this setting in a help string */
    def helpSyntax: String = name
    /** A description of the purpose of this setting in a help string */
    def helpDescription = descr
    /** Return a list of strings that can be used to recreate the receiver's current setting. */
    def unparse: List[String]

    protected var setByUser: Boolean = false
    
    def isDefault: Boolean = !setByUser

    protected[Settings] var dependency: Option[(Setting, String)] = None
    
    def dependsOn(s: Setting, value: String): this.type = { dependency = Some((s, value)); this }
    
    def dependsOn(s: Setting): this.type = dependsOn(s, "")

    def isStandard: Boolean = !isAdvanced && !isPrivate && !(name eq "-Y")
    
    def isAdvanced: Boolean = (name startsWith "-X") && !(name eq "-X")
    
    def isPrivate: Boolean =  (name == "-P") || ((name startsWith "-Y") && !(name eq "-Y"))

    // initialization
    allsettings = this :: allsettings
  }

  /** A setting represented by a boolean flag (false, unless set) */
  case class BooleanSetting(name: String, descr: String) extends Setting(descr) {
    protected var v: Boolean = false

    def value: Boolean = this.v
    def value_=(s: Boolean) { setByUser = true; this.v = s }

    def tryToSet(args: List[String]): List[String] = args match {
      case n :: rest if (n == name) => value = true; rest
      case _ => args
    }

    def unparse: List[String] = if (value) List(name) else Nil
    
    override def equals(that: Any) = that match {
      case bs:BooleanSetting => this.name == bs.name && this.value == bs.value
      case _ => false
    }
    
  }

  /** A setting represented by a string, (`default' unless set) */
  case class StringSetting(name: String, arg: String, descr: String, default: String)
  extends Setting(descr) { 
    def abbreviation: String = null

    protected var v: String = default

    def value: String = this.v
    def value_=(s: String) { setByUser = true; this.v = s }

    def tryToSet(args: List[String]): List[String] = args match {
      case n :: rest if (name == n || abbreviation == n) =>
        if (rest.isEmpty) {
          error("missing argument")
          args
        } else {
          value = rest.head
          rest.tail
        }
      case _ => args
    }

    override def helpSyntax = name + " <" + arg + ">"

    def unparse: List[String] = if (value == default) Nil else List(name, value)
    
    override def equals(that: Any) = that match {
      case ss:StringSetting => this.name == ss.name && this.value == ss.value
      case _ => false
    }
    
  }

  /** A setting that accumulates all strings supplied to it */
  case class MultiStringSetting(name: String, arg: String, descr: String)
  extends Setting(descr) {
    protected var v: List[String] = Nil
    def value = v
    def appendToValue(str: String) { v = v ::: List(str) }
    
    protected val nameColon = name + ":"
    def tryToSet(args: List[String]): List[String] = args match {
      case arg :: rest if (arg startsWith nameColon) =>
        val toadd = arg.substring(nameColon.length())
        if (toadd.length == 0) {
          error("empty argument to " + nameColon)
          args
        } else {
          appendToValue(toadd)
          rest
        }

      case opt :: arg :: rest if (opt == name) =>
        appendToValue(arg)
        rest

      case _ => args
    }

    override def helpSyntax = name + ":<" + arg + ">"

    def unparse: List[String] =
      for (opt <- value) yield nameColon+opt
    
    override def equals(that: Any) = that match {
      case mss:MultiStringSetting =>
        this.name == mss.name &&
        this.value.length == mss.value.length &&
        List.forall2(this.value.sort(_<_), mss.value.sort(_<_))(_==_)
      case _ => false
    }
    
  }

  /** A setting represented by a string in a given set of <code>choices</code>, (<code>default</code> unless set). */
  case class ChoiceSetting(name: String, descr: String, choices: List[String], default: String)
  extends Setting(descr + choices.mkString(" (", ",", ")")) {
    protected var v: String = default

    def value: String = this.v
    def value_=(s: String) { setByUser = true; this.v = s }

    protected def argument: String = name.substring(1)

    def tryToSet(args: List[String]): List[String] = args match {
      case n :: rest if (n startsWith (name + ":")) =>
        val choice = n.substring(name.length() + 1)
        if (!(choices contains choice)) {
          error(
            if (choice == "") "missing " + argument
            else "unknown " + argument + " '" + choice + "'")
          args
        } else {
          value = choice
          rest
        }
      case n :: choice :: rest if n == name => // alternative to be consistent with IDE
        if (!(choices contains choice)) {
          error(
            if (choice == "") "missing " + argument
            else "unknown " + argument + " '" + choice + "'")
          args
        } else {
          value = choice
          rest
        }
      case _ => args
    }

    override def helpSyntax = name + ":<" + argument + ">"

    def unparse: List[String] = if (value == default) Nil else List(name + ":" + value)
    
    override def equals(that: Any) = that match {
      case cs:ChoiceSetting => this.name == cs.name && this.value == cs.value
      case _ => false
    }
    
  }

  /** Same as ChoiceSetting but have a <code>level</code> int which tells the index of the selected choice. The <code>defaultEmpty</code> is used
   * when this setting is used without specifying any of the available choices. */
  class DebugSetting(name: String, descr: String, choices: List[String], default: String, defaultEmpty: String)
  extends ChoiceSetting(name, descr, choices, default) {

    def indexOf[a](xs: List[a], e: a): Option[Int] = xs match {
      case y :: ys => if (e == y) Some(0) else indexOf(ys, e) match {
          case Some(idx) => Some(1 + idx)
          case None => None
        }
      case _ => None
    }
    var level: Int = indexOf(choices, default).get

    override def value_=(choice: String) {
      setByUser = true
      this.v = choice
      this.level = indexOf(choices, choice).get
    }

    override def tryToSet(args: List[String]): List[String] = args match {
      case n :: rest if (n startsWith (name + ":")) =>
        val choice = n.substring(name.length() + 1)
        if (!(choices contains choice)) {
          error(
              if (choice == "") "missing " + argument
              else "unknown " + argument + " '" + choice + "'")
          args
        } else {
          value = choice
          rest
        }

      case n :: rest if (n startsWith name) =>
        value = defaultEmpty
        rest

      case _ => args
    }
  }
}
